package com.yeskery.nut.extend.actuator;

import com.yeskery.nut.application.ApplicationType;
import com.yeskery.nut.application.ServerEventContext;
import com.yeskery.nut.application.ServerStatus;
import com.yeskery.nut.bean.BeanDefinition;
import com.yeskery.nut.bean.BeanIterable;
import com.yeskery.nut.core.*;
import com.yeskery.nut.plugin.NutApplicationSupportBasePlugin;
import com.yeskery.nut.plugin.Plugin;
import com.yeskery.nut.plugin.PluginManager;
import com.yeskery.nut.plugin.ServerEventPlugin;
import com.yeskery.nut.websocket.WebSocketConfiguration;
import com.yeskery.nut.websocket.WebSocketRegisterFactory;

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryType;
import java.lang.management.MemoryUsage;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Logger;

/**
 * 应用管理端口插件
 * @author sprout
 * 2022-06-01 10:26
 */
public class ActuatorPlugin extends NutApplicationSupportBasePlugin implements ServerEventPlugin {

    /** 日志对象 */
    private static final Logger logger = Logger.getLogger(ActuatorPlugin.class.getName());

    /** 默认URI */
    private static final String ACTUATOR_BASE_URI = "/actuator";

    /** 访问URI */
    private final String baseUri;

    /**
     * 构建应用管理端口插件
     */
    public ActuatorPlugin() {
        this(ACTUATOR_BASE_URI);
    }

    /**
     * 构建应用管理端口插件
     * @param baseUri 访问基础URI
     */
    public ActuatorPlugin(String baseUri) {
        this.baseUri = baseUri;
    }

    @Override
    public void beforeStart(ServerEventContext serverEventContext) {
        ApplicationType applicationType = serverEventContext.getApplicationMetadata().getApplicationType();
        if (ApplicationType.isWebApplicationType(applicationType)) {
            ControllerManager controllerManager = serverEventContext.getControllerManager();
            controllerManager.registerController(serverStatusRequestHandler(), Method.GET, baseUri + "/health", new String[] {baseUri + "/status"});
            controllerManager.registerController(controllerInfoRequestHandler(), Method.GET, baseUri + "/controllers");
            controllerManager.registerController(websocketInfoRequestHandler(), Method.GET, baseUri + "/websockets");
            controllerManager.registerController(beanInfoRequestHandler(), Method.GET, baseUri + "/beans");
            controllerManager.registerController(pluginInfoRequestHandler(), Method.GET, baseUri + "/plugins");
            controllerManager.registerController(systemRequestHandler(), Method.GET, baseUri + "/system");
            controllerManager.registerController(memoryRequestHandler(), Method.GET, baseUri + "/memory");
            controllerManager.registerController(shutdownRequestHandler(), Method.PUT, baseUri + "/shutdown");

            logger.info("Actuator[Health], Endpoint[" + baseUri + "/health], Alias["
                    + String.join(",", new String[] {baseUri + "/status"}) + "], Method[" + Method.GET + "]");
            logger.info("Actuator[Controller], Endpoint[" + baseUri + "/controllers], Alias[], Method[" + Method.GET + "]");
            logger.info("Actuator[WebSocket], Endpoint[" + baseUri + "/websockets], Alias[], Method[" + Method.GET + "]");
            logger.info("Actuator[Bean], Endpoint[" + baseUri + "/beans], Alias[], Method[" + Method.GET + "]");
            logger.info("Actuator[Plugin], Endpoint[" + baseUri + "/plugins], Alias[], Method[" + Method.GET + "]");
            logger.info("Actuator[System], Endpoint[" + baseUri + "/system], Alias[], Method[" + Method.GET + "]");
            logger.info("Actuator[Memory], Endpoint[" + baseUri + "/memory], Alias[], Method[" + Method.GET + "]");
            logger.info("Actuator[Shutdown], Endpoint[" + baseUri + "/shutdown], Alias[], Method[" + Method.PUT + "]");
        }
    }

    @Override
    public void afterStart(ServerEventContext serverEventContext) {
        ControllerManager controllerManager = serverEventContext.getControllerManager();
        for (Plugin plugin : serverEventContext.getPluginManager().getAllPlugins()) {
            if (plugin instanceof Actuate) {
                for (ActuateMedata actuateMedata : ((Actuate) plugin).getActuateMedata()) {
                    controllerManager.registerController(actuateMedata.getRequestHandler(), actuateMedata.getMethod(), baseUri + actuateMedata.getUri());
                    logger.info("Actuator[" + actuateMedata.getName() + "], Endpoint[" + baseUri + actuateMedata.getUri()
                            + "], Alias[], Method[" + actuateMedata.getMethod() + "]");
                }
            }
        }
    }

    @Override
    public int getOrder() {
        return Order.MIN;
    }

    /**
     * 服务状态端点
     * @return 服务状态端点处理类
     */
    private RequestHandler serverStatusRequestHandler() {
        return (request, response, execution) -> {
            String status;
            if (getNutApplication().getNutStatus() == ServerStatus.STARTING.getStatus()) {
                status = "STARTING";
            } else if (getNutApplication().getNutStatus() == ServerStatus.STARTED.getStatus()) {
                status = "RUNNING";
            } else if (getNutApplication().getNutStatus() == ServerStatus.CLOSING.getStatus()) {
                status = "CLOSING";
            } else if (getNutApplication().getNutStatus() == ServerStatus.CLOSED.getStatus()) {
                status = "CLOSED";
            } else {
                status = "UNKNOWN";
            }
            response.writeJson("{\"status\": \"" + status + "\"}");
        };
    }

    /**
     * controller信息端点
     * @return controller信息端点处理类
     */
    private RequestHandler controllerInfoRequestHandler() {
        return (request, response, execution) -> {
            ControllerManager controllerManager = getNutApplication().getNutWebConfigure().getControllerManager();
            StringBuilder xml = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Server>\n");
            if (controllerManager.getAllController().isEmpty()) {
                xml.append("  <Controllers/>\n");
            } else {
                xml.append("  <Controllers>\n");
                for (Map.Entry<ControllerMetadata, Controller> entry : controllerManager.getAllController().entrySet()) {
                    xml.append("    <Controller>\n");
                    xml.append("      <id>").append(entry.getKey().getId()).append("</id>\n");
                    xml.append("      <path>").append(entry.getKey().getPath()).append("</path>\n");
                    if (entry.getKey().getAlias().length == 0) {
                        xml.append("      <alias/>\n");
                    } else {
                        xml.append("      <alias>").append(String.join(",", entry.getKey().getAlias())).append("</alias>\n");
                    }
                    xml.append("      <source>").append(entry.getKey().getControllerSource()).append("</source>\n");
                    xml.append("      <entity>").append(entry.getValue()).append("</entity>\n");
                    xml.append("      <class>").append(entry.getValue().getClass().getName()).append("</class>\n");
                    xml.append("    </Controller>\n");
                }
                xml.append("  </Controllers>\n");
            }
            if (controllerManager.getStaticResourcePaths().isEmpty()) {
                xml.append("  <StaticPath/>\n");
            } else {
                xml.append("  <StaticPath>\n");
                for (Path path : controllerManager.getStaticResourcePaths()) {
                    xml.append("    <path>").append(path.getPath()).append("</path>\n");
                }
                xml.append("  </StaticPath>\n");
            }
            if (controllerManager.getStaticResourceDirs().isEmpty()) {
                xml.append("  <StaticDir/>\n");
            } else {
                xml.append("  <StaticDir>\n");
                for (String dir : controllerManager.getStaticResourceDirs()) {
                    xml.append("    <dir>").append(dir).append("</dir>\n");
                }
                xml.append("  </StaticDir>\n");
            }
            xml.append("</Server>");
            response.writeXml(xml.toString());
        };
    }

    /**
     * webSocket信息端点
     * @return webSocket信息端点处理类
     */
    private RequestHandler websocketInfoRequestHandler() {
        return (request, response, execution) -> {
            Collection<WebSocketConfiguration> webSocketConfigurations = Collections.emptySet();
            WebSocketRegisterFactory webSocketRegisterFactory = getApplicationContext().getBean(WebSocketRegisterFactory.class);
            if (webSocketRegisterFactory != null) {
                webSocketConfigurations = webSocketRegisterFactory.getWebSocketConfigurations();
            }
            StringBuilder xml = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Server>\n");
            if (webSocketConfigurations.isEmpty()) {
                xml.append("  <Websockets/>\n");
            } else {
                xml.append("  <Websockets>\n");
                for (WebSocketConfiguration webSocketConfiguration : webSocketConfigurations) {
                    PathMetadata pathMetadata = webSocketConfiguration.getWebSocketServerConfigure().getPathMetadata();
                    xml.append("    <Websocket>\n");
                    xml.append("      <originalPath>").append(pathMetadata.getOriginalPath()).append("</originalPath>\n");
                    xml.append("      <basePath>").append(pathMetadata.getBasePath()).append("</basePath>\n");
                    xml.append("      <dynamic>").append(pathMetadata.isDynamic()).append("</dynamic>\n");
                    if (pathMetadata.getPathPattern() == null) {
                        xml.append("      <pathPattern/>\n");
                    } else {
                        xml.append("      <pathPattern>").append(pathMetadata.getPathPattern()).append("</pathPattern>\n");
                    }
                    xml.append("      <handlerClass>").append(webSocketConfiguration.getWebSocketHandler().getClass().getName()).append("</handlerClass>\n");
                    xml.append("    </Websocket>\n");
                }
                xml.append("  </Websockets>\n");
            }
            xml.append("</Server>");
            response.writeXml(xml.toString());
        };
    }

    /**
     * bean端点信息
     * @return bean端点信息处理类
     */
    private RequestHandler beanInfoRequestHandler() {
        return (request, response, execution) -> {
            BeanIterable beanIterable = (BeanIterable) getNutApplication().getApplicationContext();
            StringBuilder xml = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
            xml.append("<Beans>\n");
            if (beanIterable.getSingletonBeans().isEmpty()) {
                xml.append("  <Singleton/>\n");
            } else {
                xml.append("  <Singleton>\n");
                for (Map.Entry<String, Object> entry : beanIterable.getSingletonBeans().entrySet()) {
                    xml.append("    <Bean>\n");
                    xml.append("      <name>").append(entry.getKey()).append("</name>\n");
                    xml.append("      <entity>").append(entry.getValue()).append("</entity>\n");
                    xml.append("      <class>").append(entry.getValue().getClass().getName()).append("</class>\n");
                    xml.append("    </Bean>\n");
                }
                xml.append("  </Singleton>\n");
            }
            if (beanIterable.getPrototypeBeanDefinitions().isEmpty()) {
                xml.append("  <Prototype/>\n");
            } else {
                xml.append("  <Prototype>\n");
                for (Map.Entry<String, BeanDefinition> entry : beanIterable.getPrototypeBeanDefinitions().entrySet()) {
                    xml.append("    <Bean>\n");
                    xml.append("      <name>").append(entry.getKey()).append("</name>\n");
                    xml.append("      <entity>").append(entry.getValue()).append("</entity>\n");
                    xml.append("      <class>").append(entry.getValue().getBeanClass().getName()).append("</class>\n");
                    xml.append("    </Bean>\n");
                }
                xml.append("  </Prototype>\n");
            }
            xml.append("</Beans>");
            response.writeXml(xml.toString());
        };
    }

    /**
     * plugin信息端点
     * @return plugin信息端点处理类
     */
    private RequestHandler pluginInfoRequestHandler() {
        return (request, response, execution) -> {
            PluginManager pluginManager = getNutApplication().getNutWebConfigure().getPluginManager();
            StringBuilder xml = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
            if (pluginManager.getAllPlugins().isEmpty()) {
                xml.append("<Plugins/>\n");
            } else {
                xml.append("<Plugins>\n");
                for (Plugin plugin : pluginManager.getAllPlugins()) {
                    xml.append("  <Plugin>\n");
                    xml.append("    <source>").append(plugin).append("</source>\n");
                    xml.append("    <class>").append(plugin.getClass().getName()).append("</class>\n");
                    xml.append("  </Plugin>\n");
                }
                xml.append("</Plugins>");
            }
            response.writeXml(xml.toString());
        };
    }

    /**
     * 系统信息端点
     * @return 系统信息端点处理类
     */
    private RequestHandler systemRequestHandler() {
        return (request, response, execution) -> {
            Runtime runtime = Runtime.getRuntime();
            Properties props = System.getProperties();
            StringBuilder xml = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Server>\n");
            if (props.isEmpty()) {
                xml.append("  <System/>\n");
            } else {
                xml.append("  <System>\n");
                for (Map.Entry<Object, Object> entry : props.entrySet()) {
                    xml.append("    <").append(entry.getKey()).append(">").append(entry.getValue())
                            .append("</").append(entry.getKey()).append(">\n");
                }
                xml.append("  </System>\n");
            }
            xml.append("  <Runtime>\n");
            xml.append("    <totalMemory>").append(runtime.totalMemory()).append("</totalMemory>\n");
            xml.append("    <maxMemory>").append(runtime.maxMemory()).append("</maxMemory>\n");
            xml.append("    <freeMemory>").append(runtime.freeMemory()).append("</freeMemory>\n");
            xml.append("    <availableProcessors>").append(runtime.availableProcessors()).append("</availableProcessors>\n");
            xml.append("  </Runtime>\n");
            xml.append("</Server>");
            response.writeXml(xml.toString());
        };
    }

    /**
     * 内存信息端点
     * @return 内存信息端点处理类
     */
    private RequestHandler memoryRequestHandler() {
        return (request, response, execution) -> {
            MemoryUsage heapMemoryUsage = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
            MemoryUsage nonHeapMemoryUsage = ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage();

            StringBuilder xml = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Memory>\n");
            xml.append("  <Heap>\n");
            xml.append("    <jvm.heap.init>").append(heapMemoryUsage.getInit()).append("</jvm.heap.init>\n");
            xml.append("    <jvm.heap.used>").append(heapMemoryUsage.getInit()).append("</jvm.heap.used>\n");
            xml.append("    <jvm.heap.committed>").append(heapMemoryUsage.getInit()).append("</jvm.heap.committed>\n");
            xml.append("    <jvm.heap.max>").append(heapMemoryUsage.getInit()).append("</jvm.heap.max>\n");
            xml.append("  </Heap>\n");
            xml.append("  <NonHeap>\n");
            xml.append("    <jvm.nonheap.init>").append(nonHeapMemoryUsage.getInit()).append("</jvm.nonheap.init>\n");
            xml.append("    <jvm.nonheap.used>").append(nonHeapMemoryUsage.getInit()).append("</jvm.nonheap.used>\n");
            xml.append("    <jvm.nonheap.committed>").append(nonHeapMemoryUsage.getInit()).append("</jvm.nonheap.committed>\n");
            xml.append("    <jvm.nonheap.max>").append(nonHeapMemoryUsage.getInit()).append("</jvm.nonheap.max>\n");
            xml.append("  </NonHeap>\n");
            if (ManagementFactory.getMemoryPoolMXBeans().isEmpty()) {
                xml.append("  <GC/>\n");
            } else {
                xml.append("  <GC>\n");
                for (MemoryPoolMXBean pool : ManagementFactory.getMemoryPoolMXBeans()) {
                    final String kind = pool.getType() == MemoryType.HEAP ? "heap" : "nonheap";
                    final String poolName = pool.getName().replace(" ", ".");
                    final MemoryUsage usage = pool.getUsage();
                    xml.append("    <").append(kind).append(">\n");
                    xml.append("      <jvm.").append(poolName).append(".init>").append(usage.getInit()).append("</jvm.").append(poolName).append(".init>\n");
                    xml.append("      <jvm.").append(poolName).append(".used>").append(usage.getUsed()).append("</jvm.").append(poolName).append(".used>\n");
                    xml.append("      <jvm.").append(poolName).append(".committed>").append(usage.getCommitted()).append("</jvm.").append(poolName).append(".committed>\n");
                    xml.append("      <jvm.").append(poolName).append(".max>").append(usage.getMax()).append("</jvm.").append(poolName).append(".max>\n");
                    xml.append("    </").append(kind).append(">\n");
                }
                xml.append("  </GC>\n");
            }
            xml.append("</Memory>");
            response.writeXml(xml.toString());
        };
    }

    /**
     * 系统关闭端点
     * @return 系统关闭端点处理类
     */
    private RequestHandler shutdownRequestHandler() {
        return (request, response, execution) -> {
            response.writeJson("{\"status\": \"CLOSING\"}");
            getNutApplication().close();
        };
    }
}
